package com.bix.util.blizfiles.m2;

import java.nio.ByteBuffer;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import com.jme.math.Matrix4f;
import com.jme.math.Quaternion;
import com.jme.math.Vector3f;


/**
 * This class encapsulates information about a "bone" in a M2 file. Most of the
 * information was obtained from the wiki found at: {@link http
 * ://madx.dk/wowdev/wiki/index.php?title=M2/WotLK#Bones}
 * <p>
 * As this structure is generally useful only when reading from M2 files and
 * it's subordinates, there are no setters, only getters to retrieve information
 * about the structure of a model. Consumers of this class would likely create
 * adapter or proxy classes to convert them into something more useful based on
 * whatever API they are using.
 * 
 * @author squid
 * 
 * @version 1.0.0
 */
public class M2Bone {
	private Log log = LogFactory.getLog (M2Bone.class);

	//
	// Index into Animation sequences or -1.
	//
	private int geosetAnim;

	//
	// Only known flags: 8 - billborded and 512 - transformed.
	//
	private int flags;

	//
	// Parent bone ID or -1 if there is none.
	//
	private short parentBoneId;
	private M2Bone parentBone;

	//
	// A geoset for this bone.
	//
	// TODO: Not sure what this means exactly.
	//
	private short geosetId;

	//
	// 4 bytes that have been in since TBC. No one knows what they are for.
	//
	private int unknown2;

	//
	// An animation block for translation. Animations blocks are actually
	// used as references to other data. In this case, the data that the
	// animation block points to should be a list of 3 * floats.
	//
	private Vector3f transformation;
	private M2VectorAnimationBlock transformationAnimation;

	//
	// An animation block for rotation. Should be 4*shorts, see Quaternion values.
	// and 2.x. The shorts become floats once they have been read and converted
	// from shorts.
	//
	private Quaternion rotation;
	private M2QuaternionAnimationBlock rotationAnimation;

	//
	// An animation block for scaling. The animation block should point to a list
	// of 3 * floats.
	//
	private Vector3f scaling;
	private M2VectorAnimationBlock scalingAnimation;

	//
	// The pivot point of that bone.
	//
	private Vector3f pivotPoint;

	//
	// Name of the bone.
	//
	private String name;

	public M2Bone(String name) {
		this.name = name;
	}

	public M2Bone(String name, ByteBuffer bb) {
		this.name = name;
		read (bb);
	}

	public void read (ByteBuffer bb) {
		this.geosetAnim = bb.getInt ();
		this.flags = bb.getInt ();
		this.parentBoneId = bb.getShort ();
		this.geosetId = bb.getShort ();
		this.unknown2 = bb.getInt ();
		log.debug ("  geosetAnim[" + this.getGeosetAnim () + "]");
		log.debug ("  flags[0x" + Integer.toHexString (this.getFlags ()) + "]");
		log.debug ("  parentBoneId[" + this.getParentBoneId () + "]");
		log.debug ("  geosetId[" + this.getGeosetId () + "]");
		log.debug ("  unknown2[0x" + Integer.toHexString (this.getUnknown2 ()) + "]");
		log.debug ("  transformationAnimation:");
		this.transformationAnimation = new M2VectorAnimationBlock (bb);
		log.debug ("  rotationAnimation:");
		this.rotationAnimation = new M2QuaternionAnimationBlock (bb);
		log.debug ("  scalingAnimation:");
		this.scalingAnimation = new M2VectorAnimationBlock (bb);
		this.pivotPoint = new Vector3f ();
		this.pivotPoint.x = bb.getFloat ();
		this.pivotPoint.y = bb.getFloat ();
		this.pivotPoint.z = bb.getFloat ();
		log.debug ("  pivotPoint[" + this.getPivotPoint ().getX () + "," + this.getPivotPoint ().getY () + ","
				+ this.getPivotPoint ().getZ () + "]");
	}

	public String getName () {
		return this.name;
	}

	/**
	 * @return the geosetAnim
	 */
	public int getGeosetAnim () {
		return geosetAnim;
	}

	/**
	 * @return the parentBoneId
	 */
	public short getParentBoneId () {
		return parentBoneId;
	}

	/**
	 * @return the geosetId
	 */
	public short getGeosetId () {
		return geosetId;
	}

	/**
	 * @return the unknown2
	 */
	public int getUnknown2 () {
		return unknown2;
	}

	/**
	 * @return the transform
	 */
	public Vector3f getTransformation () {
		return transformation;
	}

	/**
	 * @return the rotation
	 */
	public Quaternion getRotation () {
		return rotation;
	}

	/**
	 * @return the scaling
	 */
	public Vector3f getScaling () {
		return scaling;
	}

	/**
	 * @return the flags
	 */
	public int getFlags () {
		return flags;
	}

	/**
	 * @return the pivotPoint
	 */
	public Vector3f getPivotPoint () {
		return pivotPoint;
	}

	/**
	 * Calculate the matrix for a given animation sequence and time.
	 * 
	 * @param animationSequence
	 *          The animation sequence to calculate the matrix for.
	 * @param time
	 *          The time into the animation sequence.
	 * @return
	 */
	public	Matrix4f	lastCalcMatrix;
	
	public	Vector3f	lastTranslation;
	public	Quaternion	lastRotation;
	public	Vector3f	lastScaling;
	
	public Matrix4f calculateMatrixJme (int animationSequence, int time) {
		boolean didTranslation = false;
		boolean didRotation = false;
		boolean didScaling = false;
		Matrix4f m = new Matrix4f ();
		m.setTranslation (this.pivotPoint.x, this.pivotPoint.y, this.pivotPoint.z);

		//
		// Get the translation, rotation, and scaling vectors for the given
		// animation sequence and time.  These values will be interpolated between
		// the two frames that "surround" the time.
		//
		Vector3f transVec = this.getTransformationAnimation ().getKeyFrameDataValue (animationSequence, time);
		Quaternion rotQuat = this.getRotationAnimation ().getKeyFrameDataValue (animationSequence, time);
		Vector3f scaleVec = this.getScalingAnimation ().getKeyFrameDataValue (animationSequence, time);

		//
		// Calculate the translation part of the animation.
		//
		com.jme.math.Matrix4f transMat = new com.jme.math.Matrix4f ();
		if (transVec != null) {
			//
			// Set the translation for the matrix.
			//
			transMat.setTranslation (transVec);
			m = m.mult (transMat);
			didTranslation = true;
			this.lastTranslation = transVec;
		} else {
			this.lastTranslation = new Vector3f ();
		}
			
		
		//
		// Calculate the rotation part of the animation. Get the rotation quaternion
		// for the given animation sequence.
		//
		com.jme.math.Matrix4f rotMat = new com.jme.math.Matrix4f ();
		if (rotQuat != null) {
			
			//
			// Get the rotation quaternion for this frame.
			//
			rotMat.setRotationQuaternion (rotQuat);
			m = m.mult (rotMat);
			didRotation = true;
			this.lastRotation = rotQuat;
		} else {
			this.lastRotation = new Quaternion ();
		}

		//
		// Calculate the scaling part of the animation.
		//
		if (scaleVec != null) {
			if (scaleVec.x > 10) {
				scaleVec.x = 1.0f;
			}
			if (scaleVec.y > 10) {
				scaleVec.y = 1.0f;
			}
			if (scaleVec.z > 10) {
				scaleVec.z = 1.0f;
			}
			
			Matrix4f	scaleMat = new Matrix4f ();
			scaleMat.scale(scaleVec);
			m = m.mult(scaleMat);
			
			//scaleMat.m00 = scaleVec.x;
			//scaleMat.m11 = scaleVec.y;
			//scaleMat.m22 = scaleVec.z;
			//scaleMat.m33 = 1.0f;
			//this.lastScaling = new Vector3f (1.0f, 1.0f, 1.0f);
			this.lastScaling = scaleVec;
			
			didScaling = true;
		} else {
			this.lastScaling = new Vector3f (1.0f, 1.0f, 1.0f);
		}
		
		//
		// If we didn't do anything, return a matrix set to an identity matrix.
		//
		if (didTranslation == false && didRotation == false && didScaling == false) {
			m = new com.jme.math.Matrix4f ();
		} else {
			//
			// Finish up...
			//
			com.jme.math.Vector3f unpivot = new com.jme.math.Vector3f (-this.pivotPoint.x, -this.pivotPoint.y, -this.pivotPoint.z);
			com.jme.math.Matrix4f unpiv = new com.jme.math.Matrix4f ();
			unpiv.setTranslation (unpivot);
			m = m.mult (unpiv);
		}

		//
		// Save the matrix.
		//
		this.lastCalcMatrix = m;
		return m;
	}
	/*

	private Point3f currTranslation = null;
	private Quat4f currRot = null;
	private Point3f currScale = null;
	public Matrix4f	matrix = null;

	public void test (int animationSequence, int time) {
		Point3f	transPivot = new Point3f ();
		Matrix4f	rotMatrix;

		com.jme.math.Vector3f vec = this.getTransformationAnimation ().getKeyFrameDataValue (animationSequence, time);
		com.jme.math.Quaternion quat = this.getRotationAnimation ().getKeyFrameDataValue (animationSequence, time);
		com.jme.math.Vector3f svec = this.getScalingAnimation ().getKeyFrameDataValue (animationSequence, time);

		
		Matrix4f m = new Matrix4f();
		Matrix4f tmpMat = null;
		Quat4f q = null;
		
		if (vec == null || quat == null || svec == null) {
			if (matrix == null) {
				matrix = new Matrix4f();
				rotMatrix = new Matrix4f();
				matrix.setIdentity();
				rotMatrix.setIdentity();
			}
			currTranslation = null;
			currScale = null;
			currRot = null;
			return;
		}
		
		
		boolean doesSomething = vec != null || quat != null || svec != null;
		if (doesSomething) {
			m.setIdentity();
			m.setTranslation(new Vector3f(this.pivotPoint));
			
			if (vec != null) {
				Point3f tr = new Point3f (vec.x, vec.y, vec.z);
				tmpMat = new Matrix4f();
				tmpMat.setIdentity();
				tmpMat.setTranslation(new Vector3f(tr));
				m.mul(tmpMat);
				currTranslation = tr;
			} else {
				currTranslation = null;
			}
			
			if (quat != null) {
				q = new Quat4f (quat.x, quat.y, quat.z, quat.w);
				tmpMat = new Matrix4f();
				tmpMat.setIdentity();
				tmpMat.setRotation(q);
				tmpMat.transpose();
				m.mul(tmpMat);
				currRot = q;
			} else {
				currRot = null;
			}
			
			if (svec != null) {
				Point3f sc = new Point3f (svec.x, svec.y, svec.z);
				if (sc.x > 10F)
					sc.x = 1.0F;
				if (sc.y > 10F)
					sc.y = 1.0F;
				if (sc.z > 10F)
					sc.z = 1.0F;
				tmpMat = new Matrix4f();
				tmpMat.setIdentity();
				tmpMat.m00 = sc.x;
				tmpMat.m11 = sc.y;
				tmpMat.m22 = sc.z;
				tmpMat.m33 = 1.0F;
				tmpMat.setScale(sc.x);
				m.mul(tmpMat);
				currScale = sc;
			} else {
				currScale = null;
			}
			
			Point3f unpivot = new Point3f(this.pivotPoint);
			unpivot.scale(-1F);
			tmpMat = new Matrix4f();
			tmpMat.setIdentity();
			tmpMat.setTranslation(new Vector3f(unpivot));
			m.mul(tmpMat);
		} else {
			currTranslation = currScale = null;
			currRot = null;
			m.setIdentity();
		}
		
		
		if (this.parentBoneId > -1) {
			matrix = this.parentBone.calculateMatrix (animationSequence, time);
			matrix.mul(m);
		} else {
			matrix = m;
		}
		
		if (quat != null) {
			if (this.parentBoneId > -1) {
				rotMatrix = this.parentBone.calculateMatrix (animationSequence, time);
				Matrix4f mat = new Matrix4f();
				mat.setIdentity();
				mat.setRotation(q);
				mat.transpose();
				rotMatrix.mul(mat);
			} else {
				rotMatrix = new Matrix4f();
				rotMatrix.setIdentity();
				rotMatrix.setRotation(q);
				rotMatrix.transpose();
			}
		} else {
			rotMatrix = new Matrix4f();
			rotMatrix.setIdentity();
		}		
		
		transPivot.set(this.pivotPoint);
		matrix.transform(transPivot);

	}

	public com.jme.math.Matrix4f calculateMatrix (int animationSequence, int time, boolean useJme) {
		if (useJme) {
			return this.calculateMatrixJme (animationSequence, time);
		} else {
			return J3dToJme.matrix4f (this.calculateMatrix (animationSequence, time));
		}
	}
	 */
	
	/**
	 * @return the transformationAnimation
	 */
	public M2VectorAnimationBlock getTransformationAnimation () {
		return transformationAnimation;
	}

	/**
	 * @return the rotationAnimation
	 */
	public M2QuaternionAnimationBlock getRotationAnimation () {
		return rotationAnimation;
	}

	/**
	 * @return the scalingAnimation
	 */
	public M2VectorAnimationBlock getScalingAnimation () {
		return scalingAnimation;
	}

	/**
	 * @return the parentBone
	 */
	public M2Bone getParentBone () {
		return parentBone;
	}

	/**
	 * @param parentBone
	 *          the parentBone to set
	 */
	public void setParentBone (M2Bone parentBone) {
		this.parentBone = parentBone;
	}
}
